#include "bitops.h"
#include "viogpu.h"

#if !DBG
#include "bitops.tmh"
#endif

#pragma code_seg(push)
#pragma code_seg()

VOID GetPitches(_In_ CONST BLT_INFO *pBltInfo, _Out_ LONG *pPixelPitch, _Out_ LONG *pRowPitch)
{
    switch (pBltInfo->Rotation)
    {
        case D3DKMDT_VPPR_IDENTITY:
            *pPixelPitch = (pBltInfo->BitsPerPel / BITS_PER_BYTE);
            *pRowPitch = pBltInfo->Pitch;
            return;
        case D3DKMDT_VPPR_ROTATE90:
            *pPixelPitch = -((LONG)pBltInfo->Pitch);
            *pRowPitch = (pBltInfo->BitsPerPel / BITS_PER_BYTE);
            return;
        case D3DKMDT_VPPR_ROTATE180:
            *pPixelPitch = -((LONG)pBltInfo->BitsPerPel / BITS_PER_BYTE);
            *pRowPitch = -((LONG)pBltInfo->Pitch);
            return;
        case D3DKMDT_VPPR_ROTATE270:
            *pPixelPitch = pBltInfo->Pitch;
            *pRowPitch = -((LONG)pBltInfo->BitsPerPel / BITS_PER_BYTE);
            return;
        default:
            VioGpuDbgBreak();
            VIOGPU_LOG_ASSERTION1("Invalid rotation (0x%I64x) specified", pBltInfo->Rotation);
            *pPixelPitch = 0;
            *pRowPitch = 0;
            return;
    }
}

BYTE *GetRowStart(_In_ CONST BLT_INFO *pBltInfo, _In_ CONST RECT *pRect)
{
    BYTE *pRet = NULL;
    LONG OffLeft = pRect->left + pBltInfo->Offset.x;
    LONG OffTop = pRect->top + pBltInfo->Offset.y;
    LONG BytesPerPixel = (pBltInfo->BitsPerPel / BITS_PER_BYTE);

    switch (pBltInfo->Rotation)
    {
        case D3DKMDT_VPPR_IDENTITY:
            pRet = ((BYTE *)pBltInfo->pBits + (ULONGLONG)OffTop * pBltInfo->Pitch + (ULONGLONG)OffLeft * BytesPerPixel);
            break;
        case D3DKMDT_VPPR_ROTATE90:
            pRet = ((BYTE *)pBltInfo->pBits + ((ULONGLONG)pBltInfo->Height - 1 - OffLeft) * pBltInfo->Pitch +
                    (ULONGLONG)OffTop * BytesPerPixel);
            break;
        case D3DKMDT_VPPR_ROTATE180:
            pRet = ((BYTE *)pBltInfo->pBits + ((ULONGLONG)pBltInfo->Height - 1 - OffTop) * pBltInfo->Pitch +
                    ((ULONGLONG)pBltInfo->Width - 1 - OffLeft) * BytesPerPixel);
            break;
        case D3DKMDT_VPPR_ROTATE270:
            pRet = ((PBYTE)pBltInfo->pBits + (ULONGLONG)OffLeft * pBltInfo->Pitch +
                    ((ULONGLONG)pBltInfo->Width - 1 - OffTop) * BytesPerPixel);
            break;
        default:
            {
                VIOGPU_LOG_ASSERTION1("Invalid rotation (0x%I64x) specified", pBltInfo->Rotation);
            }
    }

    return pRet;
}

VOID CopyBitsGeneric(_In_ BLT_INFO *pDst, _In_ CONST BLT_INFO *pSrc, _In_ CONST RECT *pRect)
{
    LONG DstPixelPitch = 0;
    LONG DstRowPitch = 0;
    LONG SrcPixelPitch = 0;
    LONG SrcRowPitch = 0;

    DbgPrint(TRACE_LEVEL_VERBOSE, ("---> %s Dst = %p Src = %p\n", __FUNCTION__, pDst->pBits, pSrc->pBits));

    GetPitches(pDst, &DstPixelPitch, &DstRowPitch);
    GetPitches(pSrc, &SrcPixelPitch, &SrcRowPitch);

    VIOGPU_ASSERT(pRect->right >= pRect->left);
    VIOGPU_ASSERT(pRect->bottom >= pRect->top);

    UINT NumPixels = pRect->right - pRect->left;
    UINT NumRows = pRect->bottom - pRect->top;

    BYTE *pDstRow = GetRowStart(pDst, pRect);
    CONST BYTE *pSrcRow = GetRowStart(pSrc, pRect);

    for (UINT y = 0; y < NumRows; y++)
    {
        BYTE *pDstPixel = pDstRow;
        CONST BYTE *pSrcPixel = pSrcRow;

        for (UINT x = 0; x < NumPixels; x++)
        {
            if (pDst->BitsPerPel == 32 && pSrc->BitsPerPel == 32)
            {
                UINT32 *pDstPixelAs32 = (UINT32 *)pDstPixel;
                UINT32 *pSrcPixelAs32 = (UINT32 *)pSrcPixel;
                *pDstPixelAs32 = *pSrcPixelAs32;
            }
            else if ((pDst->BitsPerPel == 24) || (pSrc->BitsPerPel == 24))
            {
                pDstPixel[0] = pSrcPixel[0];
                pDstPixel[1] = pSrcPixel[1];
                pDstPixel[2] = pSrcPixel[2];
            }
            else
            {
                VioGpuDbgBreak();
            }
            pDstPixel += DstPixelPitch;
            pSrcPixel += SrcPixelPitch;
        }
        pDstRow += DstRowPitch;
        pSrcRow += SrcRowPitch;
    }
}

VOID CopyBits32_32(_In_ BLT_INFO *pDst, _In_ CONST BLT_INFO *pSrc, _In_ CONST RECT *pRect)
{
    VIOGPU_ASSERT((pDst->BitsPerPel == 32) && (pSrc->BitsPerPel == 32));
    VIOGPU_ASSERT((pDst->Rotation == D3DKMDT_VPPR_IDENTITY) && (pSrc->Rotation == D3DKMDT_VPPR_IDENTITY));

    DbgPrint(TRACE_LEVEL_VERBOSE, ("---> %s\n", __FUNCTION__));

    VIOGPU_ASSERT(pRect->right >= pRect->left);
    VIOGPU_ASSERT(pRect->bottom >= pRect->top);

    UINT NumPixels = pRect->right - pRect->left;
    UINT NumRows = pRect->bottom - pRect->top;
    UINT BytesToCopy = NumPixels * 4;
    BYTE *pStartDst = ((BYTE *)pDst->pBits + ((ULONGLONG)pRect->top + pDst->Offset.y) * pDst->Pitch +
                       ((ULONGLONG)pRect->left + pDst->Offset.x) * 4);
    CONST BYTE *pStartSrc = ((BYTE *)pSrc->pBits + ((ULONGLONG)pRect->top + pSrc->Offset.y) * pSrc->Pitch +
                             ((ULONGLONG)pRect->left + pSrc->Offset.x) * 4);

    for (UINT i = 0; i < NumRows; ++i)
    {
        RtlCopyMemory(pStartDst, pStartSrc, BytesToCopy);
        pStartDst += pDst->Pitch;
        pStartSrc += pSrc->Pitch;
    }
}

VOID BltBits(_In_ BLT_INFO *pDst, _In_ CONST BLT_INFO *pSrc, _In_ CONST RECT *pRect)
{
    DbgPrint(TRACE_LEVEL_VERBOSE, ("---> %s\n", __FUNCTION__));
    __try
    {
        if ((pDst->Rotation == D3DKMDT_VPPR_IDENTITY && pSrc->Rotation == D3DKMDT_VPPR_IDENTITY) &&
            (pDst->BitsPerPel == 32 && pSrc->BitsPerPel == 32))
        {
            CopyBits32_32(pDst, pSrc, pRect);
        }
        else
        {
            CopyBitsGeneric(pDst, pSrc, pRect);
        }
    }
#pragma prefast(suppress : __WARNING_EXCEPTIONEXECUTEHANDLER, "try/except is only able to protect against user-mode errors and these are the only errors we try to catch here");
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        DbgPrint(TRACE_LEVEL_ERROR,
                 ("Either dst (0x%p) or src (0x%p) bits encountered exception during access.\n",
                  pDst->pBits,
                  pSrc->pBits));
    }
}

UINT BPPFromPixelFormat(_In_ D3DDDIFORMAT Format)
{
    switch (Format)
    {
        case D3DDDIFMT_UNKNOWN:
            return 0;
        case D3DDDIFMT_A1:
            return 1;
        case D3DDDIFMT_P8:
            return 8;
        case D3DDDIFMT_R5G6B5:
            return 16;
        case D3DDDIFMT_R8G8B8:
            return 24;
        case D3DDDIFMT_X8R8G8B8:
        case D3DDDIFMT_A8R8G8B8:
            return 32;
        default:
            VIOGPU_LOG_ASSERTION1("Unknown D3DDDIFORMAT 0x%I64x", Format);
            VioGpuDbgBreak();
            return 32;
    }
}

#pragma code_seg(pop) // End Non-Paged Code
